﻿using System;
using System.Reflection;
using System.Reflection.Emit;

namespace MyBasicLib.Reflection.FastReflection.Emit
{

    public class MethodInvoker : IMethodInvoker 
    {
        private readonly Func<object, object[], object> m_invoker;

        public MethodInvoker(MethodInfo methodInfo)
        {
            //this.methodInfo = methodInfo;
            m_invoker = CreateInvokeDelegate(methodInfo);
        }

        #region IMethodInvoker Members

        public object Invoke(object instance, params object[] parameters)
        {
            return m_invoker(instance, parameters);
        }

        #endregion
        private static Func<object, object[], object> CreateInvokeDelegate(MethodInfo methodInfo)
        { 
            DynamicMethod dynamicMethod;
            if (methodInfo.IsPublic)
            {
                dynamicMethod = new DynamicMethod(methodInfo.Name,
                                                  typeof(object), new[]
                                                                       {
                                                                           typeof (object),
                                                                           typeof (object[])
                                                                       },
                                                methodInfo.Module);
                // methodInfo.DeclaringType); //.DeclaringType);
            }
            else
            {
                dynamicMethod = new DynamicMethod(methodInfo.Name,
                                                  typeof(object), new[]
                                                                       {
                                                                           typeof (object),
                                                                           typeof (object[])
                                                                       },
                                                  methodInfo.DeclaringType); //.DeclaringType);
            }
            ILGenerator il = dynamicMethod.GetILGenerator();
            ParameterInfo[] ps = methodInfo.GetParameters();
            var paramTypes = new Type[ps.Length +
                (!methodInfo.IsStatic ? 1 : 0)];//处理 valuetype
            for (int i = 0; i < paramTypes.Length; i++)
            {
                paramTypes[i] = i == ps.Length ? methodInfo.DeclaringType : (
                    ps[i].ParameterType.IsByRef ? ps[i].ParameterType.GetElementType() :
                    ps[i].ParameterType);
            }
            var locals = new LocalBuilder[paramTypes.Length];
            for (int i = 0; i < paramTypes.Length; i++)
            {
                locals[i] = il.DeclareLocal(paramTypes[i],true);
            }
            for (int i = 0; i < ps.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_1);
                il.EmitFastInt(i);
                il.Emit(OpCodes.Ldelem_Ref);
                il.EmitCastToReference(paramTypes[i]);
                il.Emit(OpCodes.Stloc, locals[i]);
            }
            if (!methodInfo.IsStatic)
            {
                il.Emit(OpCodes.Ldarg_0);
                if (methodInfo.DeclaringType.IsValueType)//处理 valuetype
                {
                    il.Emit(OpCodes.Unbox_Any, methodInfo.DeclaringType);
                    il.Emit(OpCodes.Stloc, locals[locals.Length - 1]);
                    il.Emit(OpCodes.Ldloca_S, locals.Length - 1);
                }
            }
            for (int i = 0; i < ps.Length; i++)
            {
                if (ps[i].ParameterType.IsByRef)
                    il.Emit(OpCodes.Ldloca_S, locals[i]);
                else
                    il.Emit(OpCodes.Ldloc, locals[i]); 
            }
            if (methodInfo.IsVirtual)
                il.EmitCall(OpCodes.Callvirt, methodInfo, null);
            else
                il.EmitCall(OpCodes.Call, methodInfo, null);

            if (methodInfo.ReturnType == typeof(void))
                il.Emit(OpCodes.Ldnull);
            else
                il.EmitBoxIfNeeded(methodInfo.ReturnType);

            for (int i = 0; i < ps.Length; i++)
            {
                if (ps[i].ParameterType.IsByRef)
                {
                    il.Emit(OpCodes.Ldarg_1);
                    il.EmitFastInt(i);
                    il.Emit(OpCodes.Ldloc, locals[i]);
                    il.EmitBoxIfNeeded(locals[i].LocalType); 
                    il.Emit(OpCodes.Stelem_Ref);
                }
            }
            il.Emit(OpCodes.Ret);
            var invoder =
                dynamicMethod.CreateDelegate(typeof(Func<object, object[], object>)) as Func<object, object[], object>;
            return invoder;
        }
    }
}